# coding=utf-8
"""
作者：董新强
创建时间：
描述：
"""

import base64
###########################################################################
## Class HttpDoc
###########################################################################
import hashlib
import ujson as json
import os
import re
import sqlite3
import time
from datetime import datetime
from random import sample as randomsample
from urllib.parse import quote, unquote

import psutil as psutil
import wx
import wx.xrc

from aes_cypto import AesCrypto
from http_doc_template import HttpDocTemplate
from http_helper import HttpHelper
from utils import to_timestamp


def to_dict(param):
    try:
        if not param: return {}
        if isinstance(param, dict): return param

        return json.loads(param.replace("'", '"'))
    except:
        return {}


def to_int(s):
    try:
        return int(s)
    except:
        return 0


class HttpDoc(wx.Frame):

    def __init__(self, parent):
        self._api_dic = {}
        wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition, size=wx.Size(1265, 840), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))

        # icon = wx.Icon('yy.ico', wx.BITMAP_TYPE_ICO)
        # self.SetIcon(icon)

        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)

        self.lb_authorization = wx.StaticText(self, wx.ID_ANY, u"域【变】", (10, 12), (60, 25))

        self.ck_main = wx.RadioButton(self, -1, "", pos=(80, 12), size=(20, 20))
        self.ck_fu = wx.RadioButton(self, -1, "", pos=(530, 12), size=(20, 20))
        self.txt_host_main = wx.TextCtrl(self, wx.ID_ANY, 'http://127.0.0.1:8000', (100, 10), (350, 25), 0)

        self.lb_host = wx.StaticText(self, wx.ID_ANY, u"域【恒】", (465, 12), (60, 25), 0)

        self.txt_host_fu = wx.TextCtrl(self, wx.ID_ANY, 'http://127.0.0.1:8000', (550, 10), (350, 25), 0)
        self.txt_authorization = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, (100, 100), (610, 25), 0)

        self.m_staticText4 = wx.StaticText(self, wx.ID_ANY, u"URI", (10, 40), (75, 25), 0)
        self.m_staticText4.Wrap(-1)

        self.txt_api = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, (100, 40), (700, 25), 0)
        api_ls = self.load_configs()
        self.drp_apis = wx.Choice(self, wx.ID_ANY, pos=(100, 70), size=(610, 25), choices=api_ls)
        self.drp_apis.Bind(wx.EVT_CHOICE, self.on_select_url)

        self.txt_process = wx.TextCtrl(self, wx.ID_ANY, 'run.py', (810, 40), (90, 25), 0)

        self.drp_method = wx.Choice(self, wx.ID_ANY, pos=(720, 70), size=(180, 25), choices=['GET', 'POST', 'DELETE', 'PUT'])
        self.drp_method.Bind(wx.EVT_CHOICE, self.on_method_choice)

        self.btn_http_request = wx.Button(self, wx.ID_ANY, u"HTTP", (950, 10), (75, 55), 0)

        self.btn_find = wx.Button(self, wx.ID_ANY, u"find", (1150, 10), (40, 25), 0)
        self.btn_kill = wx.Button(self, wx.ID_ANY, u"kill", (1198, 10), (40, 25), 0)

        self.btn_clean = wx.Button(self, wx.ID_ANY, u"Clean", (1050, 10), (75, 25), 0)

        self.btn_gen_Key = wx.Button(self, wx.ID_ANY, u"AesKey", (1050, 40), (75, 25), 0)

        self.btn_aes_encrypt = wx.Button(self, wx.ID_ANY, u"AesE", (1150, 40), (40, 25), 0)
        self.btn_aes_decrypt = wx.Button(self, wx.ID_ANY, u"AesD", (1198, 40), (40, 25), 0)

        self.btn_doc = wx.Button(self, wx.ID_ANY, u"接口文档", (1050, 70), (75, 25), 0)
        self.btn_format_raw = wx.Button(self, wx.ID_ANY, u"Fmt", (950, 70), (35, 25), 0)
        self.btn_format_with_covert_to_double_quote = wx.Button(self, wx.ID_ANY, u"Fmt2", (990, 70), (35, 25), 0)

        self.btn_encode = wx.Button(self, wx.ID_ANY, u"UriE", (1150, 70), (40, 25), 0)
        self.btn_decode = wx.Button(self, wx.ID_ANY, u"UriD", (1198, 70), (40, 25), 0)

        #
        self.btn_md5 = wx.Button(self, wx.ID_ANY, u"md5", (950, 100), (35, 25), 0)
        self.btn_sh256 = wx.Button(self, wx.ID_ANY, u"s256", (990, 100), (35, 25), 0)

        self.btn_to_timestamp = wx.Button(self, wx.ID_ANY, u"戳", (1050, 100), (35, 25), 0)
        self.btn_to_time = wx.Button(self, wx.ID_ANY, u"时", (1090, 100), (35, 25), 0)

        self.btn_b64_encrypt = wx.Button(self, wx.ID_ANY, u"B64E", (1150, 100), (40, 25), 0)
        self.btn_b64_decrypt = wx.Button(self, wx.ID_ANY, u"B64D", (1198, 100), (40, 25), 0)

        self.btn_open_file = wx.Button(self, wx.ID_ANY, u"打开文件", (720, 100), (80, 25), 0)
        self.btn_sqlalchemy = wx.Button(self, wx.ID_ANY, u"PyFmt", (820, 100), (80, 25), 0)
        #

        self.m_staticText5 = wx.StaticText(self, wx.ID_ANY, u"Authorition", (10, 103), (75, 25), 0)
        self.m_staticText5.Wrap(-1)

        self.txt_reqest_params = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, (10, 130), (700, 250), style=wx.TE_MULTILINE)

        self.txt_expection = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, (720, 130), (520, 250), style=wx.TE_MULTILINE)

        self.m_staticText6 = wx.StaticText(self, wx.ID_ANY, u"响应", (10, 400), (75, 25), 0)
        self.m_staticText6.Wrap(-1)

        self.m_staticTextTimecostTitle = wx.StaticText(self, wx.ID_ANY, u"耗时", (100, 400), (75, 25), 0)
        self.lbTimeCost = wx.StaticText(self, wx.ID_ANY, u"", (130, 400), (75, 25), 0)

        self.lbHost = wx.StaticText(self, wx.ID_ANY, u"", (250, 400), (300, 25), 0)

        self.st_parse_cost = wx.StaticText(self, wx.ID_ANY, u"解析耗时", (700, 400), (75, 25), 0)
        self.lb_parse_cost = wx.StaticText(self, wx.ID_ANY, u"112", (750, 400), (75, 25), 0)

        self.txt_response = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, (10, 430), (1230, 360), style=wx.TE_MULTILINE)
        font = wx.Font(14, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
        font_small = wx.Font(9, wx.MODERN, wx.NORMAL, wx.NORMAL, False)

        self.txt_response.SetFont(font)
        self.txt_expection.SetFont(font_small)
        self.txt_reqest_params.SetFont(font)

        self.Layout()

        self.Centre(wx.BOTH)

        # Connect Events
        self.btn_http_request.Bind(wx.EVT_BUTTON, self.to_http)
        self.btn_find.Bind(wx.EVT_BUTTON, self.get_process)
        self.btn_kill.Bind(wx.EVT_BUTTON, self.kill_process)
        self.btn_clean.Bind(wx.EVT_BUTTON, self.to_clean)
        self.btn_doc.Bind(wx.EVT_BUTTON, self.to_doc)
        self.btn_format_with_covert_to_double_quote.Bind(wx.EVT_BUTTON, self.convert_to_double_quotes)
        self.btn_format_raw.Bind(wx.EVT_BUTTON, self.to_format)
        self.btn_encode.Bind(wx.EVT_BUTTON, self.to_quote)
        self.btn_decode.Bind(wx.EVT_BUTTON, self.to_unquote)
        self.btn_sqlalchemy.Bind(wx.EVT_BUTTON, self.to_format_sqlalchemy)
        self.btn_open_file.Bind(wx.EVT_BUTTON, self.to_open_file)
        self.btn_aes_decrypt.Bind(wx.EVT_BUTTON, self.aes_decrypt)
        self.btn_aes_encrypt.Bind(wx.EVT_BUTTON, self.aes_encrypt)
        self.btn_gen_Key.Bind(wx.EVT_BUTTON, self.gen_key)

        self.Bind(wx.EVT_CLOSE, self.onClose)

        self.btn_to_time.Bind(wx.EVT_BUTTON, self.fn_to_datetime)
        self.btn_to_timestamp.Bind(wx.EVT_BUTTON, self.fn_to_timestamp)
        self.btn_b64_encrypt.Bind(wx.EVT_BUTTON, self.fn_b64_encrypt)
        self.btn_b64_decrypt.Bind(wx.EVT_BUTTON, self.fn_b64_dencrypt)

        self.btn_md5.Bind(wx.EVT_BUTTON, self.to_md5)
        self.btn_sh256.Bind(wx.EVT_BUTTON, self.to_sha256)

        self.ck_main.Bind(wx.EVT_RADIOBUTTON, self.select_main)
        self.ck_fu.Bind(wx.EVT_RADIOBUTTON, self.select_fu)

        self.http = HttpHelper()
        self._response = None

        self._method = ''
        self._comment_pattern_ = re.compile('(//.*)|(#.*)')

        self.txt_expection.SetValue('sql参数')
        self._cost_seconds = ''
        self._resp_header = {}
        self._host = 'Z'
        self._process_id = 0

    def __del__(self):
        pass

    @property
    def host(self):
        var = self.txt_host_main.GetValue() if self._host == 'Z' else self.txt_host_fu.GetValue()
        return '' if not var else var

    @property
    def api(self):
        var = self.txt_api.GetValue()
        if not var:
            return ''
        return var

    @property
    def authorization(self):
        var = self.txt_authorization.GetValue()
        if not var:
            return ''
        return var

    @property
    def req_params(self):
        self.txt_expection.SetValue("")

        var = self.txt_reqest_params.GetValue()
        if not var:
            return {}

        return json.loads(''.join(var))

    def set_response_txt(self):
        st = time.time()
        try:
            if isinstance(self._response, (dict, list)):
                self.txt_response.SetValue(HttpDocTemplate.format(self._response))
                return
            self.txt_response.SetValue(self._response)
        except Exception as e:
            print(e)
        finally:
            self.lbTimeCost.SetLabel(self._cost_seconds + '秒' if self._cost_seconds else '未知')
            self.lbHost.SetLabel(f'{"【变】" if self._host == "Z" else "【恒】"} {self.host}{self.txt_api.GetValue()}')
            self._cost_seconds = ''

            self.txt_expection.SetValue(HttpDocTemplate.format(self._resp_header))
            self._resp_header = {}
            self.lb_parse_cost.SetLabel('%.3f'%(time.time()-st))

    def to_http(self, event):
        if self._method == 'GET':
            self.to_get(event)
            return

        if self._method == 'POST':
            self.to_post(event)
            return

        if self._method == 'DELETE':
            self.to_delete(event)
            return

        if self._method == 'PUT':
            self.to_put(event)
            return

        self.txt_response.SetValue("请选择HttpMethod")

    def to_get(self, event):
        try:
            self._method = 'GET'
            self._response, self._cost_seconds, self._resp_header = self.http.get(self.host, self.api, self.req_params, self.authorization)
            self.set_response_txt()
        except Exception as e:
            self.txt_response.SetValue(str(e))
        else:
            self.save()

    def to_post(self, event):
        try:
            self._method = 'POST'
            self._response, self._cost_seconds, self._resp_header = self.http.post(self.host, self.api, self.req_params, self.authorization)
            self.set_response_txt()
        except  Exception as e:
            self.txt_response.SetValue(str(e))
        else:
            self.save()

    def to_put(self, event):
        try:
            self._method = 'PUT'
            self._response, self._cost_seconds, self._resp_header = self.http.put(self.host, self.api, self.req_params, self.authorization)
            self.set_response_txt()
        except Exception as e:
            self.txt_response.SetValue(str(e))

    def to_delete(self, event):
        try:
            self._method = 'DELETE'
            self._response, self._cost_seconds, self._resp_header = self.http.delete(self.host, self.api, self.req_params, self.authorization)
            self.set_response_txt()
        except Exception as e:
            self.txt_response.SetValue(str(e))

    def to_clean(self, event):
        self.txt_response.SetValue("")
        self._response = None
        self.txt_reqest_params.SetValue('')
        self.txt_response.SetValue('')
        self._method = ''
        self.txt_expection.SetValue('')
        self._process_id = 0

    def to_doc(self, event):
        try:
            params = HttpDocTemplate.format(self.get_request_params())
            response = HttpDocTemplate.format(json.loads(self.txt_response.GetValue().replace('false','0').replace('true','1')))
            api = self.api if self.api else self.host

            if not self._method:
                self._method = 'POST'

            self.txt_response.SetValue(HttpDocTemplate.get_content(api, self._method, params, response))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def get_request_params(self):
        try:
            return json.loads(self.txt_reqest_params.GetValue())
        except:
            url = self.txt_api.GetValue()
            if '?' not in url: return {}

            dic = {}
            for ps in url.split('?')[-1].split('&'):
                ps = ps.split('=')
                dic[ps[0]] = ps[1]
            return dic

    def convert_to_double_quotes(self, event):
        try:
            var = self.txt_reqest_params.GetValue()
            if not var:
                return

            var = var.replace("'", "\"")
            ls = []
            for v in var.split('\n'):
                ls.append(self._comment_pattern_.sub('', v))
            self.txt_reqest_params.SetValue(HttpDocTemplate.format(json.loads(''.join(ls))))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def to_format(self, event):
        try:
            var = self.txt_reqest_params.GetValue()
            if not var:
                return

            ls = []
            for v in var.split('\n'):
                ls.append(self._comment_pattern_.sub('', v))
            self.txt_reqest_params.SetValue(HttpDocTemplate.format(json.loads(''.join(ls))))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def to_quote(self, event):
        try:
            var = self.txt_reqest_params.GetValue()
            if not var:
                return

            self._response = None
            self.txt_response.SetValue(quote(var))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def to_unquote(self, event):
        try:
            var = self.txt_reqest_params.GetValue()
            if not var:
                return

            self._response = None
            self.txt_response.SetValue(unquote(var))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def load_configs(self):
        api_ls = []
        try:
            sql = "select api,method,params from apis"
            for api, method, params in HttpDoc.select_as_list(sql):
                if api in self._api_dic:
                    self._api_dic[api][method] = to_dict(params)
                else:
                    api_ls.append(api)
                    self._api_dic[api] = {method: to_dict(params)}

            api_ls = list(api_ls)

            sql = "select token from tokens where id='dev'"
            for token in HttpDoc.select_as_list(sql):
                self.txt_authorization.SetValue(token[0])
                break
        except:
            HttpDoc.create_db()
        return api_ls

    def onClose(self, event):
        self.save()
        self.save_token()
        self.Destroy()

    def on_select_url(self, event):
        # 保持选择之前的状态
        self.save()
        self.txt_api.SetValue(self.drp_apis.GetStringSelection())
        self.fill()

    def fill(self):
        params_dic = self._api_dic.get(self.txt_api.GetValue())
        if not params_dic:
            return

        params_dic = params_dic.get(self._method)
        if not params_dic:
            return

        self.txt_host_main.SetValue(params_dic['host'])
        self.txt_api.SetValue(params_dic['url'])
        self.txt_reqest_params.SetValue(HttpDocTemplate.format(params_dic['params']))

    def on_method_choice(self, event):
        self._method = self.drp_method.GetStringSelection()
        self.fill()

    def to_format_sqlalchemy(self, event):
        try:
            sql = self.txt_reqest_params.GetValue()
            dic = to_dict(self.txt_expection.GetValue())
            if not sql or not dic:
                self.txt_expection.SetValue(sql)
            else:
                self.txt_response.SetValue(sql % dic)
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    @staticmethod
    def select_as_list(sql: str):
        ls = []
        conn = sqlite3.connect('dev.db')
        try:
            c = conn.cursor()
            cursor = c.execute(sql)
            for row in cursor:
                ls.append(row)
        finally:
            conn.close()
        return ls

    @staticmethod
    def upate(sqls: list):
        conn = sqlite3.connect('dev.db')
        try:
            c = conn.cursor()
            for sql in sqls:
                c.execute(sql)
            conn.commit()
        except Exception as e:
            conn.rollback()
            return str(e)
        finally:
            conn.close()
        return '成功'

    def save(self):
        api = self.txt_api.GetValue().strip()
        if not api: return

        conn = sqlite3.connect('dev.db')
        try:
            # 查询
            item_exists = False
            c = conn.cursor()
            cursor = c.execute('select * from apis where api="%(api)s" and method="%(method)s"' % {'api': api, 'method': self._method})
            for _ in cursor: item_exists = True

            # 请求参数
            params = self.txt_reqest_params.GetValue()
            if not params:
                params = "{}"
            elif len(params) > 4096:
                params = to_dict(params) or {}
                params = json.dumps({k: '' for k in params})

            info = {
                "host": self.txt_host_main.GetValue() or self.txt_host_fu.GetValue(),
                "url": self.txt_api.GetValue(),
                "params": params
            }

            if api not in self._api_dic:
                self.drp_apis.Append(api)  # 下拉列表新增API
                self._api_dic[api] = {self._method: info}
            else:
                self._api_dic[api][self._method] = info

            params = json.dumps(info)

            if item_exists:
                c.execute("update apis set params='%(params)s' where api='%(api)s' and method='%(method)s'" % {'params': params, 'api': api, 'method': self._method})
            else:
                c.execute("insert into apis (api,params,method) values('%(api)s','%(params)s','%(method)s')" % {'params': params, 'api': api, 'method': self._method})

            conn.commit()
        except Exception as e:
            conn.rollback()
            self.txt_response.SetValue(self.txt_response.GetValue() + '\n' + str(e))
        finally:
            conn.close()

    def save_token(self):
        # 不保存Aes密钥
        if not self.host or not self.txt_api.GetValue(): return

        token = self.txt_authorization.GetValue().strip()
        if not token or len(token) == 43: return

        conn = sqlite3.connect('dev.db')
        try:
            # 查询
            item_exists = False
            c = conn.cursor()
            cursor = c.execute('select * from tokens where id="%(id)s"' % {'id': 'dev'})
            for _ in cursor: item_exists = True

            if item_exists:
                c.execute("update tokens set token='%(token)s' where id='%(id)s'" % {'token': token, 'id': 'dev'})
            else:
                c.execute("insert into tokens (id,token) values('%(id)s','%(token)s')" % {'token': token, 'id': 'dev'})

            conn.commit()
        finally:
            conn.close()

    @staticmethod
    def create_db():
        conn = sqlite3.connect("dev.db")
        cursor = conn.cursor()
        sql = "create table IF NOT EXISTS apis (api varchar(201), method varchar(7), params text, PRIMARY KEY(api, method))"
        cursor.execute(sql)
        sql = "create table IF NOT EXISTS tokens (id varchar(20) primary key, token varchar(201))"
        cursor.execute(sql)

    def to_open_file(self, event):
        with wx.FileDialog(self, "打开文件", wildcard="*.*", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL: return

            pathname = fileDialog.GetPath()
            try:
                with open(pathname, 'rb') as file:
                    base64_data = base64.b64encode(file.read()).decode()
                    self.txt_reqest_params.SetValue(json.dumps({
                        "file": base64_data
                    }))
            except Exception as e:
                self.txt_response.SetValue(str(e))

    def fn_to_datetime(self, event):
        try:
            date_array = datetime.fromtimestamp(int(self.txt_reqest_params.GetValue()))
            self.txt_response.SetValue(date_array.strftime("%Y-%m-%d %H:%M:%S"))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def fn_to_timestamp(self, event):
        try:
            val = self.txt_reqest_params.GetValue()
            if not val:
                self.txt_response.SetValue(str(int(time.time())))
            else:
                self.txt_response.SetValue(str(to_timestamp(val)))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def fn_b64_encrypt(self, event):
        try:
            self.txt_response.SetValue(base64.b64encode(self.txt_reqest_params.GetValue().encode()).decode())
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def fn_b64_dencrypt(self, event):
        try:
            self.txt_response.SetValue(base64.b64decode(self.txt_reqest_params.GetValue()))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def aes_decrypt(self, event):
        try:
            key = self.txt_authorization.GetValue()
            if not key or len(key) != 43:
                self.txt_expection.SetValue('密钥格式错误')
                return

            txt = self.txt_reqest_params.GetValue()
            if not txt:
                self.txt_expection.SetValue('输入参数错误')
                return

            self.txt_response.SetValue(AesCrypto(key).decrypt(txt))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def aes_encrypt(self, event):
        try:
            key = self.txt_authorization.GetValue()
            if not key or len(key) != 43:
                self.txt_expection.SetValue('密钥格式错误')
                return

            txt = self.txt_reqest_params.GetValue()
            if not txt:
                self.txt_expection.SetValue('输入参数错误')
                return

            self.txt_response.SetValue(AesCrypto(key).encrypt(txt))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def gen_key(self, event):
        try:
            ls = randomsample(r'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0987654321', 43)
            self.txt_response.SetValue(''.join(ls))
        except Exception as e:
            self.txt_expection.SetValue(str(e))

    def select_main(self, event):
        self._host = 'Z'

    def select_fu(self, event):
        self._host = 'F'

    def to_sha256(self, event):
        content = self.txt_reqest_params.GetValue()
        if not content: return
        self.txt_response.SetValue(hashlib.sha256(content.encode("utf8")).hexdigest())

    def to_md5(self, event):
        content = self.txt_reqest_params.GetValue()
        if not content: return
        self.txt_response.SetValue(hashlib.md5(content.encode("utf8")).hexdigest())

    def get_process(self, event):
        self._process_id = 0
        pids, process_ls, search_name = psutil.pids(), [], self.txt_process.GetValue().strip()
        for pid in pids:
            p = psutil.Process(pid)
            pname = p.name().lower()
            if 'python' not in pname: continue

            cmd_line = p.cmdline()
            if not cmd_line: continue

            skip = False
            for cl in cmd_line:
                if 'requestor' in cl:
                    skip = True

            if skip: continue

            for cl in cmd_line:
                if 'requestor' in cl: break

                if not search_name:
                    self._process_id = pid
                    process_ls.append(f'{pid} {" ".join(p.cmdline())}')
                    break

                if search_name in cl:
                    self._process_id = pid
                    process_ls.append(f'{pid} {" ".join(p.cmdline())}')
                    break

        if process_ls:
            self.txt_response.SetValue('\n'.join(process_ls))
        else:
            self.txt_response.SetValue('未找到进程')

    def kill_process(self, event):
        process_id = to_int(self.txt_process.GetValue()) or self._process_id
        if not process_id:
            self.txt_response.SetValue('未指定要杀死的进程')
            return

        try:
            resp = os.system(f'taskkill /pid {process_id} /f ')
            self.txt_response.SetValue(f'kill result : 【{resp}】')
        except Exception as e:
            self.txt_response.SetValue(str(e))
        finally:
            self._process_id = 0


if __name__ == '__main__':
    app = wx.App()
    win = HttpDoc(parent=None)
    win.Show()
    app.MainLoop()
